package com.infonova.product.jtf.blame.callgraph;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ConstantPushInstruction;
import org.apache.bcel.generic.EmptyVisitor;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.Type;

import com.infonova.product.jtf.blame.ClassFinder;
import com.infonova.product.jtf.blame.dto.CallGraph;

/**
 * The simplest of method visitors, prints any invoked method signature for all method invocations. Class copied with
 * modifications from CJKM: http://www.spinellis.gr/sw/ckjm/
 */
public class MethodVisitor extends EmptyVisitor {

    private JavaClass visitedClass;
    private MethodGen method;
    private ConstantPoolGen constants;
    private CallGraph listener;
    private int level;

    public MethodVisitor(int level, MethodGen method, JavaClass visitedClass, CallGraph listener) {
        this.visitedClass = visitedClass;
        this.method = method;
        this.constants = method.getConstantPool();
        this.listener = listener;
        this.level = level;
    }

    public void start() {
        if (method.isAbstract() || method.isNative()) {
            return;
        }
        for (InstructionHandle ih = method.getInstructionList().getStart(); ih != null; ih = ih.getNext()) {
            Instruction i = ih.getInstruction();

            short opcode = i.getOpcode();

            if (InstructionConstants.INSTRUCTIONS[opcode] == null || !isPushOrReturn(i)) {
                i.accept(this);
            }
        }
    }

    private boolean isPushOrReturn(Instruction i) {
        return (i instanceof ConstantPushInstruction) || (i instanceof ReturnInstruction);
    }


    // format = "M:" + visitedClass.getClassName() + ":" + method.getName() + " " + "(%s)%s:%s";
    @Override
    public void visitINVOKEVIRTUAL(INVOKEVIRTUAL i) {
        trackInvocation(i);
    }

    @Override
    public void visitINVOKEINTERFACE(INVOKEINTERFACE i) {
        trackInvocation(i);
    }

    @Override
    public void visitINVOKESPECIAL(INVOKESPECIAL i) {
        trackInvocation(i);
    }

    @Override
    public void visitINVOKESTATIC(INVOKESTATIC i) {
        trackInvocation(i);
    }

    private void trackInvocation(InvokeInstruction instruction) {
        String clazz = instruction.getReferenceType(constants).toString();
        String method = instruction.getMethodName(constants);
        if (clazz.startsWith("com.infonova")) {

            List<String> params = new ArrayList<String>();
            Type[] types = instruction.getArgumentTypes(constants);
            for (Type type : types) {
                params.add(type.getSignature());
            }
            listener.add(level, clazz, method, params.toArray(new String[0]));

            try {
                JavaClass callee = new ClassParser(ClassFinder.getInstance().findSourceFile(clazz)).parse();
                if (callee.isInterface()) {
                    new ClassMethodVisitor(level + 1, callee, method, listener).start();
                    callee = new ClassParser(ClassFinder.getInstance().findImplementingSourceFile(clazz)).parse();
                }
                new ClassMethodVisitor(level + 1, callee, method, listener).start();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
